#include "base/Processor.h"
#include "base/Simulator.h"
#include "base/signals/SigContext.h"
#include "base/signals/SigManager.h"
#include "base/simapi.h"
#include "base/threads/ThreadManager.h"
#include <unistd.h>

NEW_DELETE_OPERATORS_CPP(ThreadManager,1);

ThreadManager::ThreadManager (int num_cpus_) :
    threadLog("threadLog", "th", "-:w,log.sim", "threading log"),
    num_cpus(num_cpus_),
    TIDCounter((int*)CALLOC(num_cpus, sizeof(int)))
{
    for (int i = 0; i < num_cpus; i++) {
        TIDCounter[i] = i;
    }
    
    pthreadInterfaceReal = PthreadInterface::getPthreadInterface();
    // let's set thread local storage for pthreadInterfaceReal -jiwon
    pthreadInterfaceReal->init(); 
    pthreadInterfaceSim  = NULL;

    global_sim->register_isa_ext(ISA_EXT_FORK,
                                 &ThreadManager::isa_ext_fork);
    global_sim->register_isa_ext(ISA_EXT_THEXIT,
                                 &ThreadManager::isa_ext_thexit);
    global_sim->register_isa_ext(ISA_EXT_THREADID,
                                 &ThreadManager::isa_ext_threadid);
    global_sim->register_isa_ext(ISA_EXT_THEND,
                                 &ThreadManager::isa_ext_thend);

    // following is to support per-thread signal support -jiwon
    global_sim->register_isa_ext(ISA_EXT_SIGTHREAD,
                                 &ThreadManager::isa_ext_sigthread);
}

ThreadManager::~ThreadManager () {
    delete pthreadInterfaceSim;
    FREE(TIDCounter);
}

int ThreadManager::getNewTID (int procID) {
    TIDCounter[procID] += num_cpus;
    return TIDCounter[procID];
}

int ThreadManager::getProcID (int threadID) {
    return threadID % num_cpus;
}

bool ThreadManager::isEnded (int threadID) {
    if (TIDCounter[getProcID(threadID)] > threadID ||
        (TIDCounter[getProcID(threadID)] == threadID && 
         global_sim->isHalted(getProcID(threadID))))
    {
        threadLog.d("[ThreadManager] thread(%d, %d) end checked\n", getProcID(threadID), threadID);
        return true;
    }
    return false;
}
    

void ThreadManager::setPthreadInterfaceSim (PthreadInterface* pthreadInterfaceSim)
{
    this->pthreadInterfaceSim = pthreadInterfaceSim;
    PthreadInterface::setPthreadInterface(pthreadInterfaceSim);
}

void ThreadManager::goto_real ()
{
    pthreadInterfaceReal->captureSimulatedState();
    PthreadInterface::setPthreadInterface(pthreadInterfaceReal);
}

void ThreadManager::goto_sim ()
{
    global_sim->setEndDataSegment(sbrk(0));
    PthreadInterface::setPthreadInterface(pthreadInterfaceSim);
}

typedef void* (*fptr)(void*);

void thread_trampoline (void* fp, void* arglist) {
    void* result = (*(fptr)fp)(arglist);
    pthread_exit(result);
}

bool ThreadManager::isa_ext_fork (Processor* P,
                                  Reg arg0,
                                  Reg arg1,
                                  Reg* result)
{
    ThreadManager* tm = global_sim->getTM();
    Processor *idleProc;

    idleProc = global_sim->getIdleProc();
    if (idleProc == NULL) {
        *result = 0 - 1;
        return true;
    }

    idleProc->reset(ADDR_UINT64((void*)thread_trampoline), 
                    ADDR_UINT64((void*)abort), 
                    2,
                    arg0,
                    arg1,
                    SIMARG_UNUSED,
                    SIMARG_UNUSED);   

    *result = tm->getNewTID(idleProc->getID());

    tm->threadLog.d("[ThreadManager] isa_ext_fork (%d)\n", idleProc->getID());

    // this is for one in a million cases.
    // fork() is called right at the cycle quiesce is called().
    if (global_sim->isQuiesceActive()) {
        ASSERT(!global_sim->isQuiesced());
        global_sim->quiesceEntity(idleProc);
    }

    return true;
}

bool ThreadManager::isa_ext_thexit (Processor* P,
                                    Reg arg0,
                                    Reg arg1,
                                    Reg* result)
{
    ThreadManager* tm = global_sim->getTM();
    tm->threadLog.d("[ThreadManager] isa_ext_thexit (%d)\n", P->getID());

    P->halt();   
    return true;
}

bool ThreadManager::isa_ext_threadid (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    ThreadManager* tm = global_sim->getTM();
    tm->threadLog.d("[ThreadManager] isa_ext_threadid (%d)\n", P->getID());    
    *result = tm->TIDCounter[P->getID()];
    return true;
}

bool ThreadManager::isa_ext_thend (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    ThreadManager* tm = global_sim->getTM();
    *result = tm->isEnded(arg0) ? 1 : 0;
    return true;
}

bool ThreadManager::isa_ext_sigthread (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    ThreadManager* tm = global_sim->getTM();
    int destProc = tm->getProcID(arg0);
    int signum = arg1;

    SigManager* sm = global_sim->getSM();
    sm->scheduleThreadSignal(destProc, signum, NEW SigContext(signum, NULL, 0));
    return true;
}
